#!/usr/bin/env perl

# Basile Clout, October 2007
# Part of wrekaUDP

use Getopt::Long;
use Socket;
use Sys::Hostname;
use IPC::Open3;

use strict;
#use warnings;

# Local parameters
my $host = hostname();
my $ip = inet_ntoa(scalar(gethostbyname($host)));

my $ssh_bin="yes ' ' | rsh";
#my $ssh_bin="ssh"
my $ssh_user="root";

my $iperf_bin_server="/root/config/code/iperf-1.7.0/iperf";
#my $iperf_bin_server="/root/export_cluster/code/iperf-1.7.0/iperf"; # Use iperf 1.7.0 because of issues with the daemonization (-D) of 2.0.2. But issue too with 2.0.2: constantly hog CPU
# Tweak: in package, go in the cfg directory, comment out "#define HAVE_PTHREAD_CANCEL", make, go up one level, and make again (for lids)
#my $iperf_bin_client="/Users/basileclout/Desktop/iperf-1.7.0/iperf";
my $iperf_bin_client="iperf"; # Can only work with a 2.0.2 client and a 1.7.0 server!!!!
my $iperf_time=5;
my $iperf_port=5050;
my $iperf_proto="udp";
my $iperf_udp_maxbdw="100Mb";

my $ping_bin="ping";
my $ping_count=4;

my $log_file="iperf_udp.log";
my $iperf_server_list="127.0.0.1 lids";
my $iperf_server_file="";
my @iperf_servers=();

my $last_line="";
my $help;

GetOptions("t=i"=>\$iperf_time, "p=s"=>\$iperf_proto,"c=s"=>\$iperf_server_list, "f=s"=>\$iperf_server_file, "log=s"=>\$log_file, "iperf_server=s"=>\$iperf_bin_server, "iperf_client=s"=>\$iperf_bin_client, "h"=>\$help, "r=s"=>\$ssh_bin, "u=s"=>\$ssh_user);

print "Usage: ./wrekaUDP.pl [-c list of nodes] [-f file containing nodes] [-t time] [-p protocol (\"udp\" or \"tcp\")] [--log log file] [--iperf_server path of iperf on the servers] [--iperf_client path of iperf on the client] [-r network protocol (ssh, rsh, ...)] [-u user] [-h this help]\nExample: ./wrekaUDP.pl -p udp -t 3 --log mylog.log -f lamhosts.txt -r ssh -u basil\n\n" if ($help);

my $order_ping="";
my $order_iperf="";
my $order_iperf_server="";
my $tmp_file="lastid$$.tmp";
my $order_iperf_getpid="ps -C iperf -o pid= --sort=-start_time | head -1 > $tmp_file";

# Check remote connecter
if ($ssh_bin !~ /rsh/ && $ssh_bin !~ /ssh/) {
  warn "WARNING: the network protocol $ssh_bin is strange ...";
  print LOG "WARNING: the network protocol $ssh_bin is strange ...\n";
}

# Open log descriptor
open LOG, ">$log_file" or die "Impossible to open ".$log_file.": $!";
my ($sec, $min, $hrs, $day, $month, $year) = (localtime) [0,1,2,3,4,5];
my $date = sprintf("%04d-%02d-%02d %d:%d:%d\n", $year+1900, $month+1, $day, $hrs, $min, $sec);
print LOG "iperf_udp log, Basile Clout, ".$date;

# Put servers in an array
if ($iperf_server_file ne ""){
  open FILE, "<$iperf_server_file" or die "Impossible to open servers file ".$iperf_server_file.": $!";
  my @iperf_servers_line = <FILE>;
  @iperf_servers=split /\s+/, $iperf_servers_line[$#iperf_servers_line];
}else{
  @iperf_servers=split /\s+/, $iperf_server_list;
}
print LOG "Connect to:".join(' ', @iperf_servers)."\n";


# Print configuration on screen
print $host." ".$ip." ".($#iperf_servers+1)." ".$iperf_proto." ".$iperf_time."\n";


# Loop on the servers
foreach my $server (@iperf_servers){

  $server =~ s/\n//;
  if ($server eq $host){
    next;
  } 
  # Orders
  if ($iperf_proto eq "tcp"){
    $order_iperf = $iperf_bin_client." -c ".$server." -t ".$iperf_time." -p $iperf_port -f m\n";
    $order_iperf_server = "$iperf_bin_server -s -D -p $iperf_port";
  }elsif ($iperf_proto eq "udp"){
    $order_iperf = $iperf_bin_client." -u -c ".$server." -t ".$iperf_time." -b ".$iperf_udp_maxbdw." -p $iperf_port -f m\n";
    $order_iperf_server = "$iperf_bin_server -s -u -D -p $iperf_port";
  }else{
    die "ERROR: Bad protocol ".$iperf_proto;
  }
  print LOG "Order iperf:".$order_iperf;
  print LOG "Order save pid of launched iperf: $order_iperf_getpid", "\n";

  # Power on the server
  my $ssh_order_server = "$ssh_bin ${ssh_user}\@${server} \"$order_iperf_server &> /dev/null; $order_iperf_getpid\""; # Trick: Redirect stdout AND stderr to /dev/null!!
  print LOG "Power on server with \"$ssh_order_server\": ... ";
  my $pid = open3(\*IPERFS_WR, \*IPERFS, \*IPERFS_ERR, "$ssh_order_server") or die("Cannot launch iperf server on $server: $!");
  print LOG "OK!\n";
   waitpid $pid, 0;
   my @iperf_server_err=<IPERFS_ERR>;
   print LOG join(' ', @iperf_server_err);
   die "Error when launching iperf server on $server from $host: ".join(' ', @iperf_server_err)."\n" if ($#iperf_server_err>1);

  
#   print "Power on iperf server ...";
#   system($ssh_order_server) or die("Error iperf server: $!");
#   print "OK!\n";

  # Launch iperf client
  $pid=open3(\*IPERF_WR, \*IPERF, \*IPERF_ERR, "$order_iperf") or die "Cannot launch iperf test: $!";
  waitpid $pid, 0;
  my @iperf_content=<IPERF>;
  my @iperf_err=<IPERF_ERR>;
  print LOG "Output:\n\t".join(' ', @iperf_content)."\n";
  print LOG "Error:\n\t".join(' ', @iperf_err)."\n";

  # Errors?
  foreach (@iperf_err){
    if (/error/){
      die "Error iperf";
    }
  }

 # Power off the server
  $ssh_order_server = "$ssh_bin ${ssh_user}\@${server} \"kill -9 \\\`cat $tmp_file\\\`; rm -f $tmp_file\" &> /dev/null";
  print LOG "Power off server with \"$ssh_order_server\": ... ";
  $pid = open3(\*IPERFS_WR, \*IPERFS, \*IPERFS_ERR, "$ssh_order_server") or die("Cannot launch iperf server on $server: $!");
  print LOG "OK!\n";
   waitpid $pid, 0;
   @iperf_server_err=<IPERFS_ERR>;
   print LOG join(' ', @iperf_server_err);
   die "Error when launching iperf server on $server from $host: ".join(' ', @iperf_server_err)."\n" if ($#iperf_server_err>1);


  # Find the last useful line
  if ($iperf_content[$#iperf_content] =~ /Mbits\/sec/) {
    $last_line=$iperf_content[$#iperf_content];
  }else{
    $last_line=$iperf_content[$#iperf_content-1];
  }
  
  # Parse the result line
  my ($left_part, $right_part) = split /MBytes/, $last_line;
  my @right = split /\s+/, $right_part;
  my $bdw=$right[1];
  my $jitter=$right[3];

#   my $i=0;
#   foreach my $line (@right){
#     print $i, " -> ".$line."\n";
#     $i++;
#   }

  # Ping order
  $order_ping=$ping_bin." -q -n -c ".$ping_count." ".$server."\n";
  print LOG "Order ping:".$order_ping;

  # Run the ping test
  $pid = open3(\*PING_WR, \*PING, \*PING_ERR, "$order_ping") or die "Cannot launch ping test $order_ping: $!";
  waitpid $pid, 0;
  my @ping_content=<PING>;
  my @ping_err=<PING_ERR>;

  print LOG "Output: \n\t".join(' ', @ping_content)."\n";
  print LOG "Error: \n\t".join(' ', @ping_err)."\n";

  # Errors?
  foreach (@ping_err){
    if(/cannot resolve/){
      die "Error ping";
    }
  }

  ($left_part, $right_part) = split /=/, $ping_content[$#ping_content];
  @right = split /\//, $right_part;
  my $lat = $right[0]/2;
  $lat =~s/^\s+//;
  
  printf "%s %.1f %.2f\n", $server, $bdw, $lat;
}

close(LOG);
